
/*
*
*	The Gimme ECMAScript Library by Stephen Stchur
*	"Taking the pain out of Javascript!"
*
*	Copyright (c) Microsoft Corporation.  All rights reserved.
*
*
*/

Gimme.Style = new function()
{
	function getRelativePos(elem)
	{
		var useScroll = elem.scrollTop
						&& elem !== document.documentElement
						&& elem !== document.body
						&& elem.tagName !== 'TEXTAREA'
						&& elem.tagName !== 'INPUT'
						? -1 : 0;
			
		var pos =
		{
			x: elem.scrollLeft * useScroll,
			y: elem.scrollTop * useScroll
		};
		
		if (Gimme.Browser.is_ie && getRenderedStyle(elem, 'position') === 'static')
		{
			var tmp = elem.style.position;
			elem.style.position = 'relative';
			pos.x += elem.offsetLeft;
			pos.y += elem.offsetTop;
			elem.style.position = tmp;
		}
		else
		{
			pos.x += elem.offsetLeft;
			pos.y += elem.offsetTop;
		}
		
		return pos;
	};
	this.get_relative_pos = getRelativePos;

	
	function getAbsolutePos(elem)
	{
		var pos = { x: 0, y: 0 };
		
		if (elem.getBoundingClientRect)
		{
			var rect = elem.getBoundingClientRect();
			var offsets = getBoundingClientRectOffsets();
			var scrollPos = Gimme(window).get_scroll_pos();
			pos.x = rect.left - offsets.x + scrollPos.x
			pos.y = rect.top - offsets.y + scrollPos.y;
		}
		else
		{		
			var tmp;
			while (elem !== null)
			{		
				tmp = getRelativePos(elem);
				pos.x += tmp.x;
				pos.y += tmp.y;
				
				elem = elem.offsetParent;			
				if (elem && !offsetIncludesBorders())
				{
					pos.x += parseFloat(getRenderedStyle(elem, 'borderLeftWidth')) || 0;
					pos.y += parseFloat(getRenderedStyle(elem, 'borderTopWidth')) || 0;
				}			
			}
		}
		return pos;	
	}
	this.get_absolute_pos = getAbsolutePos;
	
	
	function offsetIncludesBorders()
	{
		if (typeof offsetIncludesBorders.constantValue === 'undefined')
		{
			var a = document.createElement('div');
			a.style.cssText = 'position:absolute;visibility:hidden;top:0;left:0;border:1px solid #000;';
			var b = document.createElement('div');			
			a.appendChild(b);
			document.body.appendChild(a);			
			offsetIncludesBorders.constantValue = b.offsetTop === 1;
			document.body.removeChild(a);
			a = b = null;
		}
		return offsetIncludesBorders.constantValue;
	}
	this.offset_includes_borders = offsetIncludesBorders;
	
	
	function getBoundingClientRectOffsets()
	{
		if (typeof getBoundingClientRectOffsets.constantValue === 'undefined')
		{
			// 561447: (sstchur)
			// Chrome and Firefox (and possibly Safari) will report wrong values for ancestor.getBoundingClientRect()
			// if the ancestor contains a descendant which has margins set.
			// In a really weird twist, putting a border (any border) on the ancestor, causes .getBoundingClientRect()
			// to return the correct value.
			//
			// This would be easy to fix (and IS easy to fix in Firefox and Chrome) by temporarily giving a border to
			// the ancestor (in this case the body element) and then getting the boundingClientRect, and then restoring
			// the border to its original value.
			//
			// Problem is, scrollbars can sometimes appeaer (if adding border would push contents beyond viewport boundaries)
			// in IE <= 7 (8 and above are fine).  Due to the single-threaded nature of Javascript, this set / measure / reset
			// should happen without any visual disturbance (it does in Chrome and Firefox).  And, I suspect it actually
			// does in IE as well.  However, IE can get into a state, where the setting of one property can cause scrollbars
			// to appear, and then, when said property is reset to its original value, rather then actually REMOVING scrollbars,
			// IE will instead leave them visible but just disable them (like that's the same thing -- wtf IE???)
			//
			// After a bit of investigation, it turns out that an old trick often used in IE6 to force it to repaint, actually
			// works here as well.  By setting the documentElement's className to itself, IE will update itself and the scrollbars
			// will disappear.
			//
			// Because this function computes the offsets only once and then caches the results, adding in the "reset className hack"
			// to force a refresh is acceptable since it won't happen repeatedly.

			var box = document.body;
			var pos = box.style.position;
			var margin = box.style.margin;
			var border = box.style.border;
			box.style.position = 'static';
			box.style.margin = '0';
			box.style.border = '1px solid transparent';
			var rect = box.getBoundingClientRect();
			var scrollPos = Gimme(window).get_scroll_pos();
			getBoundingClientRectOffsets.constantValue = { x: rect.left + scrollPos.x, y: rect.top + scrollPos.y };
			box.style.position = pos;
			box.style.margin = margin;
			box.style.border = border;
			document.documentElement.className = document.documentElement.className;		// save IE from itself :-/
		}
		return getBoundingClientRectOffsets.constantValue;
	}
	this.get_bounding_client_rect_offsets = getBoundingClientRectOffsets;
	

	var setOpacity = function()
	{
		function w3c(elem, opacity)
		{
			elem.style.opacity = opacity;
		}
		
		function ie(elem, opacity)
		{
			opacity *= 100;
			
			var filter;
			try				
			{
				filter = elem.filters.item('DXImageTransform.Microsoft.Alpha');
				if (opacity < 100)
				{
					filter.Opacity = opacity;
					if (!filter.enabled)
					{
						filter.enabled = true;
					}
				}
				else
				{
					filter.enabled = false;
				}
			}
			catch (ex)
			{
				if (opacity < 100)
				{
					elem.style.filter = (elem.currentStyle || elem.runtimeStyle).filter + ' progid:DXImageTransform.Microsoft.Alpha(opacity=' + opacity + ')';
				}
			}		
		}
		
		var d = document.createElement('div');
		return typeof d.style.opacity !== 'undefined' && w3c
			|| typeof d.style.filter !== 'undefined' && ie
			|| function() {};
	}();
	this.set_opacity = setOpacity;
	
	
	var getOpacity = function()
	{
		function w3c(elem)
		{
			return parseFloat(elem.style.opacity) || getRenderedStyle(elem, 'opacity');
		}
		function ie(elem)
		{
			var filter = elem.filters['DXIMageTransform.Microsoft.Alpha'];
			if (filter)
			{
				return filter.Opacity / 100;
			}
			else
			{
				return 1;
			}
		}
		
		var d = document.createElement('div');
		return typeof d.style.opacity !== 'undefined' && w3c
			|| typeof d.style.filter !== 'undefined' && ie
			|| function() {};
	}();	
	this.get_opacity = getOpacity;
	
	
	function getRenderedStyle(elem, style)
	{	
		var val = '';
		
		// can't trust .currentStyle.right or .currentStyle.bottom so special case to compute those manually
		if (elem.currentStyle && (style === 'right' || style === 'bottom'))
		{
			val = normalizeStyle(style, 'auto', 'auto', elem) + 'px';
		}
		else
		{
			var cs = (document.defaultView && document.defaultView.getComputedStyle && document.defaultView.getComputedStyle(elem, null)) || elem.currentStyle;
			if (cs)
			{			
				var val = cs[style] || '';
				var isPixels = val.match(/px$/);
				if (!isPixels)
				{
					var units = val.match(/(em|ex|%|in|cm|mm|pt|pc|small|medium|large|thin|thick|auto)$/);
					if (units)
					{
						val = normalizeStyle(style, val, units[0], elem) + 'px';
					}
				}
			}
		}
		return val;
	}
	this.get_rendered_style = getRenderedStyle;
	
	
	function normalizeStyle(style, val, units, context)
	{
		if (!val)
		{
			return 0;
		}		
		else if (units === 'px')
		{
			return parseFloat(val);
		}
		else if (val === 'auto')
		{
			// handle styles that can return a keyword value of "auto"
			switch (style)
			{
				case 'top':
				case 'left':
					return 0;
				
				// Notes about element position:
				//
				// - position: static -
				// Elements that are statically positioned are not supported here. Nothing
				// useful comes from setting the bottom/right (or top/left for that matter)
				// of a statically positioned element.
				//
				// - position: relative -
				// Elements that are relatively positioned always interpret their
				// top/right/bottom/left styles from the position they would normally
				// occupy if no position where set. The net effect of this is an
				// "opposite" relationshiop between the left/right properties and
				// the top/bottom properties of a relatively positioned element.
				// For instance, setting a relatively positioned element to either
				// top: 100px; or bottom: -100px yields the same result!
				// This comment should make clear the code below that returns the
				// negative of left when right is requested or the negative of
				// top when bottom is requested.
				//
				// - position: absolute -
				// This is the easiest case in browsers that support getComputedStyle.
				// In such cases, we don't even get to this function.  In any browser
				// that doesn't though (looking at you, IE), we have to compute right
				// (or bottom) based on a combination of the given element's left
				// position and its parent's width (or its top position and its parent's
				// height). Not too hard, but a little messy.

				// Notes about DRY (don't repeat yourself).
				//
				// The logic for right and bottom are nearly identical:
				//
				// elem_right_pos = parent_width - elem_left_pos - elem_width
				// elem_bottom_pos = parent_height - elem_top_pos - elem_height
				//
				// We don't want to duplicate the logic when the only difference
				// is a few property names.  A "propertyNames" dictionary (pn)
				// acts as an easy way to pluck out the property names we need
				// without duplicating any code					
				case 'bottom':
				case 'right':
					// PropertyNames dictionary (avoids the needs for duplicate code)
					var pn =
					{
						bottom:	[ 'top', 'offsetTop', 'height', 'offsetHeight' ],
						right: [ 'left', 'offsetLeft', 'width', 'offsetWidth' ]
					};
					
					var gim = Gimme(context);
					if (gim.get_style('position') === 'relative')
					{
						// this will "magically" be whichever one we need it to be
						var left_or_top = pn[style][0];
						
						// yes, this is on purpose (see comments above)
						return -parseInt(gim.get_style(left_or_top));
					}
					else
					{
						// need an ancestor that isn't position: static
						var parent = context.parentNode;
						while (parent !== document && Gimme(parent).get_style('position') === 'static')
						{
							parent = parent.parentNode;
						}
						
						var width_or_height = pn[style][2];
						var parentSize;
						if (parent === document)
						{
							// in which case, use the viewport instead
							parentSize = Gimme.Screen.get_viewport_size()[width_or_height];
						}
						else
						{
							// otherwise, whatever non-staic ancestor that was found will do
							parentSize = parseInt(Gimme(parent).get_style(width_or_height));
						}
						
						var offsetLeft_or_offsetTop = pn[style][1];
						var offsetWidth_or_offsetHeight = pn[style][3];
						return parentSize - context[offsetLeft_or_offsetTop] - context[offsetWidth_or_offsetHeight];
					}
					
				case 'height':
					return	context.clientHeight ||
							(context.offsetHeight && context.offsetHeight
							- parseFloat(getRenderedStyle(context, 'borderTopWidth'))
							- parseFloat(getRenderedStyle(context, 'borderBottomWidth'))) || 0;
					
				case 'width':
					return	context.clientWidth ||
							(context.offsetWidth && context.offsetWidth
							- parseFloat(getRenderedStyle(context, 'borderLeftWidth'))
							- parseFloat(getRenderedStyle(context, 'borderRightWidth'))) || 0;
					
				// TODO:
				// case 'marginLeft':
				// case 'marginTop':
				// case 'marginRight':
				// case 'marginBottom':
					// non trivial when the returned value is "auto"
					
				default:
					return 0;
			}
		}
		else
		{
			// Special case for dealing with border widths:
			// IE will return a width (possibly as a keyword rather than a numeric value)
			// even if the border style of the element in question is set to none
			switch (style)
			{
				case 'borderLeftWidth':
				case 'borderRightWidth':
				case 'borderTopWidth':
				case 'borderBottomWidth':
					style = style.replace('Width', 'Style');
					var borderStyle = getRenderedStyle(context, style);
					return borderStyle === 'none' ? 0 : parseFloat(borderStyle);
			}
			
			
			// Let the real grunt-work begin!
			context = context || document.body;
			
			// 1. Create an invisible div that we'll use to measure the style represented by the style variable, val
			// 2. Make sure its position is absolute so it doesn't shift other elements on the page
			// 3. Make sure line-height is 0 or IE will add mysterious extra space to our measurements
			var tmp = document.createElement('div');
			tmp.style.visbility = 'hidden';
			tmp.style.position = 'absolute';				
			tmp.style.lineHeight = '0';
			
			// If  we're tyring to convert a % to pixels, we have to do it in the context of a parent element
			if (units === '%')
			{
				context = context.parentNode;
				
				if (style === 'height' || style === 'top')
				{
					tmp.style.height = val;
				}
				else if (style === 'width' || style === 'left')
				{
					tmp.style.width = val;
				}
			}
			else
			{
				tmp.style.borderStyle = 'solid';
				tmp.style.borderBottomWidth = '0';					
				tmp.style.borderTopWidth = val;
			}
			
			try
			{
				context.appendChild(tmp);
			}
			catch (ex)
			{
				context = context.parentNode;
				context.appendChild(tmp);
			}
			
			
			context.offsetWidth;							// IE Hack:
			context.offsetHeight;							// just referencing these properties of the context elem, seems to force measurement of the child elements
			
			var px = tmp.offsetHeight || tmp.offsetWidth;
			context.removeChild(tmp);
			
			return px;
		}
	}
	this.normalize_style = normalizeStyle;

	var styleHash = {};
	var vendorPrefixes = [ 'O-', 'ms-', 'Moz-', 'webkit-', '' ];	
	function getCamelizedStyleName(styleName, elem)
	{
		var s = getVendorSpecificStyleInfo(styleName, elem)
		return s.camelizedName || styleName;
	}
	this.get_camelized_style_name = getCamelizedStyleName;
	
	function getCssStyleName(styleName, elem)
	{
		var s = getVendorSpecificStyleInfo(styleName, elem)
		return s.nonCamelizedName || styleName;
	}
	this.get_css_style_name = getCssStyleName;
	
	function getVendorSpecificStyleInfo(styleName, elem)
	{
		elem = elem || document.body || document.createElement('div');
		var s = styleHash[styleName];
		if (!s)
		{
			s = {};
			
			var i = vendorPrefixes.length;
			while (i--)
			{
				var prefix = vendorPrefixes[i];
				var vscsn = camelize(prefix + styleName);
				if (elem.style[vscsn] !== undefined)
				{
					var vscssn = (prefix && '-' || '') + prefix.toLowerCase() + styleName;
					s.camelizedName = vscsn;
					s.nonCamelizedName = vscssn;
					break;
				}
			}

			styleHash[styleName] = s;
		}
		
		return s;
	}
	
	function camelize(s)
	{
		return s.replace(/-(\w)/g, function(m, c)
		{
			return c.toUpperCase();
		});
	}
	this.camelize = camelize;
};

